﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Text;
using System.Reflection;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Text.RegularExpressions;

namespace AjaxSnippets
{
    /// <summary>
    /// Base class for snippets
    /// </summary>
    public abstract class Snippet : UserControl
    {
        private bool _ContainsCallbacks = false;
        private Page Holder;
        private ScriptManager GetScriptManager()
        {
            return (ScriptManager)Parent.FindControl("ScriptManager");
        }

        protected override void Render(HtmlTextWriter writer)
        {
            // Render the contents in <snippet> tag that we can use as a marker
            // to strip out the noise that ASP.NET creates which we will not be
            // making any use of
            writer.Write("<snippet>");


            // Wrap up the snippet in a div with the ClientID of this control
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            base.Render(writer);

            RenderCssIncludes(writer);

            ScriptManager sm = GetScriptManager();
            List<RegisteredScript> scriptBlocks = null;
            List<RegisteredScript> scriptStartupBlocks = null;
            if (sm != null)
            {
                object scriptRegistrations = sm.GetType().GetProperty("ScriptRegistration", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(sm, null);
                scriptBlocks = (List<RegisteredScript>)scriptRegistrations.GetType().GetProperty("ScriptBlocks").GetValue(scriptRegistrations, null);
                scriptStartupBlocks = (List<RegisteredScript>)scriptRegistrations.GetType().GetProperty("ScriptStartupBlocks").GetValue(scriptRegistrations, null);
            }

            RenderScriptIncludes(writer, scriptBlocks);

            //Make sure we have SnippetManager script registered
            RenderSnippetManagerCheckScript(writer);

            if (scriptStartupBlocks != null)
            {
                // Try to remove this component if its already been created in a previous request
                RenderComponentRemovalScripts(writer, scriptStartupBlocks);

                #region TODO: May have to enumerate descriptors and manually set them up
                //foreach (RegisteredScript startupScript in scriptStartupBlocks)
                //{
                //    if (startupScript.Control is IScriptControl)
                //    {
                //        IScriptControl scriptControl = (IScriptControl)startupScript.Control;

                //        IEnumerable<ScriptDescriptor> descriptors = scriptControl.GetScriptDescriptors();

                //        IEnumerator<ScriptDescriptor> descEnumerator = descriptors.GetEnumerator();
                //        while (descEnumerator.MoveNext())
                //        {
                //            ScriptDescriptor descriptor = descEnumerator.Current;
                //        }
                //    }
                //}
                #endregion

                foreach (RegisteredScript startupScript in scriptStartupBlocks)
                {
                    writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
                    writer.AddAttribute("language", "javascript");
                    writer.RenderBeginTag(HtmlTextWriterTag.Script);
                    // HACK: Lets replace this and have snippet manager deal with it
                    writer.Write(startupScript.Script.Replace("Sys.Application.add_init", "AjaxSnippets.SnippetManager.stackFunction"));
                    writer.RenderEndTag();
                }
            }

            // Invoke the client-side snippetRendered method if any
            writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
            writer.AddAttribute("language", "javascript");
            writer.RenderBeginTag(HtmlTextWriterTag.Script);
            writer.Write("if(typeof(snippetRendered) !== 'undefined') { snippetRendered('" + this.ID + "') }\n");
            writer.RenderEndTag();

            writer.RenderEndTag();
            writer.Write("</snippet>");
        }

        /// <summary>
        /// TODO: Refactor this to RenderHtmlLinks, attributes for rel and type can be pulled from HtmlLink
        /// </summary>
        /// <param name="writer"></param>
        private void RenderCssIncludes(HtmlTextWriter writer)
        {
            if (this.Page.Header != null)
            {
                foreach (Control control in this.Page.Header.Controls)
                {
                    // we expect AJAX Framework scripts to be already rendered since SnippetManager needs it in the hosting page
                    if (control is HtmlLink)
                    {
                        HtmlLink link = (HtmlLink)control;
                        writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
                        writer.AddAttribute("language", "javascript");
                        writer.RenderBeginTag(HtmlTextWriterTag.Script);
                        writer.Write("AjaxSnippets.SnippetManager.loadCss('" + link.Href + "');");
                        writer.RenderEndTag();
                    }
                }
            }
        }


        private void RenderScriptIncludes(HtmlTextWriter writer, List<RegisteredScript> includes)
        {
            if (includes != null)
            {
                foreach (RegisteredScript include in includes)
                {
                    // we expect AJAX Framework scripts to be already rendered since SnippetManager needs it in the hosting page
                    if (!(include.Control is ScriptManager))
                    {
                        writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
                        writer.AddAttribute("language", "javascript");
                        writer.RenderBeginTag(HtmlTextWriterTag.Script);
                        writer.Write("AjaxSnippets.SnippetManager.loadScript('" + include.Url + "');");
                        writer.RenderEndTag();
                    }
                }
            }     
        }

        private void RenderComponentRemovalScripts(HtmlTextWriter writer, List<RegisteredScript> scriptStartupBlocks)
        {
            foreach (RegisteredScript startupScript in scriptStartupBlocks)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
                writer.AddAttribute("language", "javascript");
                writer.RenderBeginTag(HtmlTextWriterTag.Script);
                writer.Write("AjaxSnippets.SnippetManager.removeComponent('" + startupScript.Control.ClientID + "');");
                writer.RenderEndTag();
            }
        }

        private void RenderSnippetManagerCheckScript(HtmlTextWriter writer)
        {
            if (_ContainsCallbacks)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
                writer.AddAttribute("language", "javascript");
                writer.RenderBeginTag(HtmlTextWriterTag.Script);
                writer.Write("if($find('SnippetManager') === null) { alert('The SnippetManager control must be added to the page'); }\n");
                writer.RenderEndTag();
            }
        }

        public override string ToString()
        {
            StringWriter writer = new StringWriter();
            HttpContext.Current.Server.Execute(Holder, writer, false);
            Regex regEx = new Regex(@"(?<=<snippet>)(.*)(?=</snippet>)", RegexOptions.Singleline);
            string result = regEx.Match(writer.ToString()).Value;
            return result;
        }

        /// <summary>
        /// Create a snippet of the given type from the given file
        /// </summary>
        /// <typeparam name="SnippetType">The Type of the snippet</typeparam>
        /// <param name="snippetId">a naming container</param>
        /// <param name="virtualPath">The virtual path to the ascx file of this snippet</param>
        /// <returns></returns>
        public static SnippetType Create<SnippetType>(string snippetId, string virtualPath) where SnippetType : Snippet
        {
            Page holder = new Page();
            // HACK: Add a HtmlHead, AjaxControlToolkit controls use this to register CSS
            holder.GetType().GetField("_header", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(holder, new HtmlHead());

            Snippet snippetControl = (Snippet)holder.LoadControl(virtualPath);
            snippetControl.ID = snippetId;
            snippetControl.Holder = holder;
            HtmlForm form = new HtmlForm();
            holder.Controls.Add(form);
            ScriptManager scriptManager = new ScriptManager();
            scriptManager.ID = "ScriptManager";
            form.Controls.Add(scriptManager);
            form.Controls.Add(snippetControl);

            return (SnippetType)snippetControl;
        }

    }
}
